//
// See the file COPYING for redistribution information.
//

#include <config.h>
#include <cstring>
#include <Naipax/Common.hpp>
#include <Naipax/Error.hpp>
#include <Naipax/Cache.hpp>

using namespace std;
using namespace Naipax;

// Note that the return value is int32_t, not uint32_t
int32_t _ComputeHash(const char *pBuf, uint32_t BufLen,
                     uint32_t CacheSlots) {
    if (pBuf == NULL) {
        return -1;
    }
    uint32_t Hash = 0;
    uint32_t x = 0;
    uint32_t i = 0;

    for (i = 0; i < BufLen; ++pBuf, ++i) {
        Hash = (Hash << 4) + (*pBuf);
        if ((x = Hash & 0xF0000000L) != 0) {
            Hash ^= (x >> 24);
        }
        Hash &= ~x;
    }

    return (int32_t) Hash % CacheSlots;
}

Cache::Cache(uint32_t KeySize, uint32_t ValueSize, uint32_t CacheSlots) {
    _mKeySize = KeySize;
    _mValueSize = ValueSize;
    _mCacheSlots = CacheSlots;

    try {
        _mpCacheTable = new CacheData[CacheSlots];
        uint32_t i = 0;
        try {
            for (i = 0; i < _mCacheSlots; ++i) {
                _mpCacheTable[i].mKeyLen = 0;
                _mpCacheTable[i].mpKey = new char[_mKeySize];
            }
            uint32_t j = 0;
            try {
                for (j = 0; j < _mCacheSlots; ++j) {
                    _mpCacheTable[j].mValueLen = 0;
                    _mpCacheTable[j].mpValue = new char[_mValueSize];
                }
            }
            catch (std::bad_alloc &E) {
                for (uint32_t k = 0; k < j; ++k) {
                    delete _mpCacheTable[k].mpValue;
                }
                NAIPAX_THROW(Error::CANNOT_INIT_CACHE);
            }
        }
        catch (std::bad_alloc &E) {
            for (uint32_t k = 0; k < i; ++k) {
                delete _mpCacheTable[k].mpKey;
            }
            NAIPAX_THROW(Error::CANNOT_INIT_CACHE);
        }
    }
    catch (std::bad_alloc &E) {
        NAIPAX_THROW(Error::CANNOT_INIT_CACHE);
    }
}

Cache::~Cache() {
    for (uint32_t i = 0; i < _mCacheSlots; ++i) {
        delete _mpCacheTable[i].mpKey;
        delete _mpCacheTable[i].mpValue;
    }
    delete[] _mpCacheTable;
}

bool Cache::Set(const void *pKey, uint32_t KeyLen,
                const void *pValue, uint32_t ValueLen,
                time_t Timeout) {
    if (pKey == NULL || pValue == NULL
        || KeyLen == 0 || KeyLen > _mKeySize || ValueLen > _mValueSize) {
        return false;
    }
    int32_t HashCode = _ComputeHash((const char*) pKey, KeyLen, _mCacheSlots);
    if (HashCode < 0) {
        return false;
    }

    CacheData *pCacheSlot = &(_mpCacheTable[HashCode]);

    memcpy(pCacheSlot->mpKey, pKey, KeyLen);
    pCacheSlot->mKeyLen = KeyLen;
    if (ValueLen > 0) {
        memcpy(pCacheSlot->mpValue, pValue, ValueLen);
    }
    pCacheSlot->mValueLen = ValueLen;
    pCacheSlot->mExpireTime = time(NULL) + Timeout;

    return true;
}

bool Cache::Get(const void *pKey, uint32_t KeyLen,
                void *pValue, uint32_t *pValueLen) {
    if (pKey == NULL || pValue == NULL || pValueLen == NULL
        || KeyLen == 0 || KeyLen > _mKeySize) {
        return false;
    }
    int32_t HashCode = _ComputeHash((const char*) pKey, KeyLen, _mCacheSlots);
    if (HashCode < 0) {
        return false;
    }
    CacheData *pCacheSlot = &(_mpCacheTable[HashCode]);
    
    if (pCacheSlot->mKeyLen == 0) {
        return false;
    }
    if (time(NULL) > pCacheSlot->mExpireTime) {
        pCacheSlot->mKeyLen = 0;
        return false;
    }
    if (KeyLen == pCacheSlot->mKeyLen
        && memcmp(pKey, pCacheSlot->mpKey, KeyLen) == 0) {
        if (pCacheSlot->mValueLen > 0) {
            memcpy((void *) pValue, pCacheSlot->mpValue,
                   pCacheSlot->mValueLen);
        }
        *pValueLen = pCacheSlot->mValueLen;
        return true;
    }
    return false;
}

bool Cache::Has(const void *pKey, uint32_t KeyLen) {
    if (pKey == NULL || KeyLen == 0 || KeyLen > _mKeySize) {
        return false;
    }

    int32_t HashCode = _ComputeHash((const char*) pKey, KeyLen, _mCacheSlots);
    if (HashCode < 0) {
        return false;
    }

    CacheData *pCacheSlot = &(_mpCacheTable[HashCode]);
    
    if (pCacheSlot->mKeyLen == 0) {
        return false;
    }
    if (time(NULL) > pCacheSlot->mExpireTime) {
        pCacheSlot->mKeyLen = 0;
        return false;
    }
    if (KeyLen == pCacheSlot->mKeyLen
        && memcmp((void*) pKey, pCacheSlot->mpKey, KeyLen) == 0) {
        return true;
    }
    return false;
}
